package com.baselib.libnetwork.net.policy;

import android.app.Activity;
import android.util.Log;

import com.baselib.libnetwork.net.HttpManager;
import com.baselib.libnetwork.net.base.Callback;
import com.baselib.libnetwork.net.base.Converter;
import com.baselib.libnetwork.net.callback.JsonCallback;
import com.baselib.libnetwork.net.model.ResultResponse;
import com.baselib.libnetwork.net.request.CustomRequest;

import java.io.IOException;
import java.net.SocketTimeoutException;

import okhttp3.Call;
import okhttp3.Response;

/**
 * @author lixuce
 * @version v1.0
 * @description
 * @since 2017/10/29
 */

public class OkhttpPolicy<T> implements HttpPolicy<T> {

    protected CustomRequest<T, ? extends CustomRequest> request;
    protected volatile boolean canceled;
    protected volatile int currentRetryCount = 0;
    protected boolean executed;
    protected okhttp3.Call rawCall;
    protected Callback<T> mCallback;

    public OkhttpPolicy(CustomRequest<T, ? extends CustomRequest> request) {
        this.request = request;
    }

    @Override
    public void requestAsync(Callback<T> callback) {
        mCallback = callback;
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    prepareRawCall();
                } catch (Throwable throwable) {
                    if (mCallback == null) {
                        return;
                    }
                    ResultResponse<T> error = ResultResponse.error(rawCall, throwable);
                    mCallback.onException(error);
                    return;
                }
                requestNetworkAsync();
            }
        });
    }

    @Override
    public ResultResponse<T> requestSync() {
        try {
            Response response = rawCall.execute();
            if (response.isSuccessful()) {
                try {
                    Converter<T> converter = request.getConverter();
                    T body = converter.convertResponse(response);

                    return ResultResponse.success(body, rawCall, response);

                } catch (Exception throwable) {
                    return ResultResponse.error(rawCall, throwable);
                }
            } else {
                return ResultResponse.error(rawCall,
                        new RuntimeException("network error! http response code is 404 or 5xx!"));
            }

        } catch (Exception throwable) {
            if (throwable instanceof SocketTimeoutException && currentRetryCount < request.getRetryCount()) {
                currentRetryCount++;
                rawCall = request.getRawCall();
                if (canceled) {
                    rawCall.cancel();
                } else {
                    requestSync();
                }
            }
            return ResultResponse.error(rawCall, throwable);
        }
    }

    void requestNetworkAsync() {
        rawCall.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                if (e instanceof SocketTimeoutException && currentRetryCount < request.getRetryCount()) {
                    //retry when timeout
                    currentRetryCount++;
                    rawCall = request.getRawCall();
                    if (canceled) {
                        rawCall.cancel();
                    } else {
                        rawCall.enqueue(this);
                    }
                } else {
                    if (!call.isCanceled()) {
                        ResultResponse<T> error = ResultResponse.error(call, e);
                        onException(error);
                    }
                }
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    try {
                        Converter<T> converter = request.getConverter();
                        if (converter == null) {
                            Log.d("http", "http request callback = null");
                            return;
                        }
                        if (converter instanceof JsonCallback) {
                            T body = converter.convertResponse(response);
                            ResultResponse<T> success = ResultResponse.success(body, call, response);
                            onSuccess(success);
                        } else {
                            onDownloadSuccess(converter.convertResponse(response));
                        }
                    } catch (Exception exception) {
                        ResultResponse<T> error = ResultResponse.error(call, exception);
                        onException(error);
                    }
                } else {
                    ResultResponse<T> error = ResultResponse.error(call,
                            new RuntimeException(String.valueOf(response.code())));
                    onException(error);
                }
            }
        });
    }


    protected void runOnUiThread(Runnable run) {

        if (request.getTag() != null && request.getTag() instanceof Activity) {
            if (((Activity) request.getTag()).isFinishing() || ((Activity) request.getTag()).isDestroyed()) {
                return;
            }
        }
        HttpManager
                .getInstance().getDelivery().post(run);
    }

    @Override
    public void onSuccess(final ResultResponse<T> success) {
        if (mCallback == null) {
            return;
        }
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mCallback.onSuccess(success);
            }
        });
    }

    @Override
    public void onException(final ResultResponse<T> error) {
        if (mCallback == null) {
            return;
        }
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mCallback.onException(error);
            }
        });
    }

    @Override
    public void onDownloadSuccess(final T success) {
        if (mCallback == null) {
            return;
        }
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mCallback.onDownloadSuccess(success);
            }
        });
    }

    @Override
    public Call prepareRawCall() throws Throwable {
        if (!executed) {
            executed = true;
            rawCall = request.getRawCall();
            if (canceled) {
                rawCall.cancel();
            }
            return rawCall;
        } else {
            return null;
        }
    }


    @Override
    public void cancel() {
        canceled = true;
        if (rawCall != null) {
            rawCall.cancel();
        }
    }

    @Override
    public boolean isCanceled() {
        if (canceled) {
            return true;
        }
        synchronized (this) {
            return rawCall != null && rawCall.isCanceled();
        }
    }
}
